using System;
using System.Collections.Generic;
using System.Runtime.Remoting;
using System.Text;

using Microsoft.Practices.EnterpriseLibrary.PolicyInjection;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.RemotingInterception;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using System.Collections.Specialized;

namespace EntLibContrib.PolicyInjection.RemotingInterception
{
    /// <summary>
    /// This class holds onto a <see cref="PolicySet" /> and can
    /// inject that policy via a remoting proxy into either a newly created object or
    /// an existing object.
    /// </summary>
    [ConfigurationElementType(typeof(CustomInjectorData))]
    public class ContextualRemotingPolicyInjector : PolicyInjector
    {
        /// <summary>
        /// Creates a new <see cref="PropertyBasedRemotingPolicyInjector" /> with an 
        /// empty <see cref="PolicySet" />.
        /// </summary>
        public ContextualRemotingPolicyInjector()
        {
        }

        /// <summary>
        /// Creates a new <see cref="PropertyBasedRemotingPolicyInjector" /> with the
        /// given <see cref="NameValueCollection"/>.
        /// </summary>
        /// <param name="attributes">Not Used</param>
        public ContextualRemotingPolicyInjector(NameValueCollection attributes)
        {
        }

        /// <summary>
        /// Creates a new <see cref="PropertyBasedRemotingPolicyInjector" /> with the
        /// given <see cref="PolicySet"/>.
        /// </summary>
        /// <param name="policies"><see cref="PolicySet"/> to use when 
        /// creating object or wrapping existing ones.</param>
        public ContextualRemotingPolicyInjector(PolicySet policies)
            : base(policies)
        {
        }

        /// <summary>
        /// Checks to see if the given type can be intercepted.
        /// </summary>
        /// <remarks>In this implementation, only interfaces and types derived from MarshalByRefObject
        /// can have policies applied.</remarks>
        /// <param name="t">Type to check.</param>
        /// <returns>True if this type can be intercepted, false if it cannot.</returns>
        public override bool TypeSupportsInterception(Type t)
        {
            return (typeof(MarshalByRefObject).IsAssignableFrom(t) || t.IsInterface);
        }

        /// <summary>
        /// Wraps the given instance in a proxy with interception hooked up if it
        /// is required by policy. If not required, returns the unwrapped instance.
        /// </summary>
        /// <param name="instance">object to wrap.</param>
        /// <param name="typeToReturn">Type of the reference to return.</param>
        /// <param name="policiesForThisType">Policy set specific to typeToReturn.</param>
        /// <returns>The object with policy added.</returns>
        protected override object DoWrap(object instance, Type typeToReturn, PolicySet policiesForThisType)
        {
            if (PolicyRequiresInterception(policiesForThisType))
            {
                ContextualRemotingRealProxy proxy =
                    new ContextualRemotingRealProxy(UnwrapTarget(instance), typeToReturn, policiesForThisType);
                return proxy.GetTransparentProxy();
            }
            return instance;
        }

        private object UnwrapTarget(object target)
        {
            if (RemotingServices.IsTransparentProxy(target))
            {
                ContextualRemotingRealProxy realProxy =
                    RemotingServices.GetRealProxy(target) as ContextualRemotingRealProxy;
                if (realProxy != null)
                {
                    return realProxy.Target;
                }
            }
            return target;
        }
    }
}
